package byx.container.core;

import byx.container.core.exception.*;
import byx.container.core.utils.CastUtils;

import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public interface Container {
    List<RegisterInfo> getRegisterInfos(Predicate<RegisterInfo> predicate);

    default RegisterInfo getRegisterInfo(Predicate<RegisterInfo> predicate) {
        List<RegisterInfo> infos = getRegisterInfos(predicate);
        if (infos.isEmpty()) {
            throw new ObjectNotFoundException(predicate);
        }
        if (infos.size() > 1) {
            throw new MultiObjectFoundException(predicate);
        }
        return infos.get(0);
    }

    default Class<?> getType(String id) {
        try {
            return getRegisterInfo(Predicates.ofId(id)).getType();
        } catch (ObjectNotFoundException e) {
            throw new IdNotFoundException(id);
        }
    }

    default Set<Class<?>> getTypes() {
        return getRegisterInfos(Predicates.ALL).stream()
                .map(RegisterInfo::getType)
                .collect(Collectors.toSet());
    }

    default ObjectFactory<?> getFactory(Predicate<RegisterInfo> predicate) {
        return getRegisterInfo(predicate).getFactory();
    }

    default List<?> getObjects(Predicate<RegisterInfo> predicate) {
        return getRegisterInfos(predicate).stream()
                .map(ri -> ri.getFactory().create())
                .collect(Collectors.toList());
    }

    default <T> T getObject(Predicate<RegisterInfo> predicate) {
        return CastUtils.cast(getRegisterInfo(predicate).getFactory().create());
    }

    default <T> T getObject(String id) {
        try {
            return getObject(Predicates.ofId(id));
        } catch (ObjectNotFoundException e) {
            throw new IdNotFoundException(id);
        }
    }

    default <T> T getObject(Class<T> type) {
        try {
            return getObject(Predicates.ofType(type));
        } catch (ObjectNotFoundException e) {
            throw new TypeNotFoundException(type);
        } catch (MultiObjectFoundException e) {
            throw new MultiTypeMatchException(type);
        }
    }

    default <T> List<T> getObjects(Class<T> type) {
        return CastUtils.cast(getObjects(Predicates.ofType(type)));
    }

    default boolean exist(Predicate<RegisterInfo> predicate) {
        return getRegisterInfos(predicate).size() > 0;
    }

    default boolean exist(String id) {
        return exist(Predicates.ofId(id));
    }

    default boolean exist(Class<?> type) {
        return exist(Predicates.ofType(type));
    }
}
